package com.wyt.cloud.utils.bean;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.wyt.cloud.utils.Fn;
import com.wyt.cloud.utils.Reflections;
import com.wyt.cloud.utils.TransferEnum;
import com.wyt.cloud.utils.exception.BusinessException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * bean操作相关工具类
 */
@Slf4j
public class BeanUtil {


    /**************************************************普通转换（开始）**************************************************************************************/
    /**
     *
     * @param src
     * @param clazz
     * @return
     * @param <T>
     */
    public static <T> T transform(Object src, Class<T> clazz) {
        if (src == null) {
            return null;
        }
        T instance;
        try {
            instance = clazz.newInstance();
        } catch (Exception e) {
            throw new BusinessException("transform error");
        }
        BeanUtils.copyProperties(src, instance, getNullPropertyNames(src));
        return instance;
    }


    private static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    public static <T> List<T> transform(List<? extends Object> srcList, final Class<T> clazz) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }

        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(transform(srcObject, clazz));
        }
        return result;
    }

    public static <T> T transformWithCustomPropertiesSet(Object source, final Class<T> targetClazz, Consumer<T> consumer) {
        T target = transform(source, targetClazz);
        consumer.accept(target);
        return target;
    }

    public static <T> List<T> transformWithCustomPropertiesSet(List<? extends Object> srcList, final Class<T> clazz, Consumer<T> consumer) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }

        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(transformWithCustomPropertiesSet(srcObject, clazz, consumer));
        }
        return result;
    }


    /**************************************************普通转换（结束）**************************************************************************************/
    /**************************************************request转BO（开始）**************************************************************************************/


    @SneakyThrows
    public static <T> T reqTransform(Object source, final Class<T> targetClazz, Function<Object, Object> enumFunc) {
        if (ObjectUtils.isEmpty(source)) {
            return null;
        }
        Objects.requireNonNull(targetClazz);
        T target = transform(source, targetClazz);

        BeanWrapper sourceInfo = new BeanWrapperImpl(source);
        BeanWrapper targetInfo = new BeanWrapperImpl(targetClazz);

        PropertyDescriptor[] srcPds = sourceInfo.getPropertyDescriptors();
        PropertyDescriptor[] tarPds = targetInfo.getPropertyDescriptors();

        for (PropertyDescriptor srcPd : srcPds) {
            Method readMethod = srcPd.getReadMethod();
            for (PropertyDescriptor tarPd : tarPds) {
                Method writeMethod = tarPd.getWriteMethod();
                Method tReadMethod = tarPd.getReadMethod();
                if (!ObjectUtils.isEmpty(readMethod.invoke(source)) & srcPd.getName().equals(tarPd.getName()) && tReadMethod.getName().equals(readMethod.getName())) {
                    if ((readMethod.getReturnType().equals(long.class) || readMethod.getReturnType().equals(Long.class)) && tReadMethod.getReturnType().equals(Date.class)) {
                        writeMethod.invoke(target, transLongToDate(sourceInfo, tarPd.getName(), readMethod, source));
                    }
                    if (readMethod.getReturnType().isEnum()) {
                        Object result = readMethod.invoke(source);
                        writeMethod.invoke(target, enumFunc.apply(result));
                    }
                }
            }
        }
        return target;
    }


    public static <T> T reqTransform(Object source, final Class<T> targetClazz) {
        return reqTransform(source, targetClazz, o -> ((TransferEnum) o).getCode());
    }


    public static <T> List<T> reqTransform(List<? extends Object> srcList, final Class<T> clazz) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }

        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(reqTransform(srcObject, clazz));
        }
        return result;
    }

    public static <T> T reqTransformWithCustomPropertiesSet(Object source, final Class<T> targetClazz, Consumer<T> consumer) {
        T target = reqTransform(source, targetClazz, o -> ((TransferEnum) o).getCode());
        consumer.accept(target);
        return target;
    }

    public static <T> List<T> reqTransformWithCustomPropertiesSet(List<? extends Object> srcList, final Class<T> clazz, Consumer<T> consumer) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }

        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(reqTransformWithCustomPropertiesSet(srcObject, clazz, consumer));
        }
        return result;
    }

    public static <K, T> T reqTransformWithCustomPropertiesSet(K src, final Class<T> clazz, BiConsumer<K, T> consumer) {
        T target = reqTransform(src, clazz);
        consumer.accept(src, target);
        return target;
    }

    public static <K, T> List<T> reqTransformWithCustomPropertiesSet(List<K> srcList, final Class<T> clazz, BiConsumer<K, T> consumer) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (K srcObject : srcList) {
            result.add(reqTransformWithCustomPropertiesSet(srcObject, clazz, consumer));
        }
        return result;
    }

    /**************************************************request转BO（结束）**************************************************************************************/
    /**************************************************BO转response（开始）**************************************************************************************/
    private static final String ENUM_GET_TYPE_METHOD = "getType";


    @SneakyThrows
    public static <T> T respTransform(Object source, final Class<T> targetClazz) {
        if (ObjectUtils.isEmpty(source)) {
            return null;
        }
        Objects.requireNonNull(targetClazz);
        T target = transform(source, targetClazz);

        BeanWrapper sourceInfo = new BeanWrapperImpl(source);
        BeanWrapper targetInfo = new BeanWrapperImpl(targetClazz);
        PropertyDescriptor[] srcPds = sourceInfo.getPropertyDescriptors();
        PropertyDescriptor[] tarPds = targetInfo.getPropertyDescriptors();

        for (PropertyDescriptor srcPd : srcPds) {
            Method readMethod = srcPd.getReadMethod();
            for (PropertyDescriptor tarPd : tarPds) {
                Method writeMethod = tarPd.getWriteMethod();
                Method tReadMethod = tarPd.getReadMethod();
                if (!ObjectUtils.isEmpty(readMethod.invoke(source)) & srcPd.getName().equals(tarPd.getName()) && tReadMethod.getName().equals(readMethod.getName())) {
                    if (readMethod.getReturnType().equals(Date.class) && (tReadMethod.getReturnType().equals(Long.class) || tReadMethod.getReturnType().equals(long.class))) {
                        writeMethod.invoke(target, transDateToLong(sourceInfo, tarPd.getName(), readMethod, source));
                    }
                    String srcValue = String.valueOf(sourceInfo.getPropertyValue(tarPd.getName()));
                    if ((Integer.class.equals(readMethod.getReturnType()) || int.class.equals(readMethod.getReturnType())) && tReadMethod.getReturnType().isEnum()) {
                        writeMethod.invoke(target, transIntToEnum(Integer.parseInt(srcValue), tarPd));
                    }
                    if (readMethod.getReturnType().equals(String.class) && tReadMethod.getReturnType().isEnum()) {
                        writeMethod.invoke(target, transStrToEnum(srcValue, tarPd));
                    }
                }
            }
        }
        return target;
    }


    private static Object transDateToLong(BeanWrapper sourceInfo, String tarPdName, Method readMethod, Object source) {
        Object srcValue = sourceInfo.getPropertyValue(tarPdName);
        Object result = null;
        if (srcValue instanceof Date) {
            try {
                result = readMethod.invoke(source);
            } catch (Exception e) {
                log.error("date 转 Long 失败，字段名称：{}", tarPdName);
            }
            result = result == null ? 0L : ((Date) result).getTime();
        }
        return result;
    }


    private static Object transLongToDate(BeanWrapper sourceInfo, String tarPdName, Method readMethod, Object source) {
        Object srcValue = sourceInfo.getPropertyValue(tarPdName);
        Object result = null;
        if (srcValue instanceof Long) {
            try {
                result = readMethod.invoke(source);
            } catch (Exception e) {
                log.error("Long 转 Date 失败，字段名称：{}", tarPdName);
            }
            result = result == null ? 0L : (new Date((Long) result));
        }
        return result;
    }

    private static Object transIntToEnum(Integer srcValue, PropertyDescriptor tarPd) {
        Class<?> propertyType = tarPd.getPropertyType();
        Object result = null;
        Method getTypeMethod;
        try {
            getTypeMethod = propertyType.getMethod(ENUM_GET_TYPE_METHOD, Integer.TYPE);
            result = getTypeMethod.invoke(propertyType, srcValue);
        } catch (Exception e) {
            try {
                getTypeMethod = propertyType.getMethod(ENUM_GET_TYPE_METHOD, Integer.class);
                result = getTypeMethod.invoke(propertyType, srcValue);
            } catch (Exception ex) {
                log.error("integer/int 转 Enum 失败，字段名称：{},err msg:{}", tarPd.getName(), ex.getMessage());
            }
        }
        return result;
    }

    private static Object transStrToEnum(String srcValue, PropertyDescriptor tarPd) {
        Class<?> propertyType = tarPd.getPropertyType();
        Object result = null;
        try {
            Method getTypeMethod = propertyType.getMethod(ENUM_GET_TYPE_METHOD, String.class);
            result = getTypeMethod.invoke(propertyType, srcValue);
        } catch (Exception e) {
            Class<Enum> targetEnum = (Class<Enum>) propertyType;
            result = Enum.valueOf(targetEnum, srcValue);
        }
        return result;
    }

    public static <T> List<T> respTransform(List<? extends Object> srcList, final Class<T> clazz) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(respTransform(srcObject, clazz));
        }
        return result;
    }

    public static <T> T respTransformWithCustomPropertiesSet(Object src, final Class<T> clazz, Consumer<T> consumer) {
        T target = respTransform(src, clazz);
        consumer.accept(target);
        return target;
    }

    public static <K, T> List<T> respTransformWithCustomPropertiesSet(List<K> srcList, final Class<T> clazz, BiConsumer<K, T> consumer) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (K srcObject : srcList) {
            result.add(respTransformWithCustomPropertiesSet(srcObject, clazz, consumer));
        }
        return result;
    }


    public static <K, T> T respTransformWithCustomPropertiesSet(K src, final Class<T> clazz, BiConsumer<K, T> consumer) {
        T target = respTransform(src, clazz);
        consumer.accept(src, target);
        return target;
    }

    public static <T> List<T> respTransformWithCustomPropertiesSet(List<? extends Object> srcList, final Class<T> clazz, Consumer<T> consumer) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> result = Lists.newArrayListWithCapacity(srcList.size());
        for (Object srcObject : srcList) {
            result.add(respTransformWithCustomPropertiesSet(srcObject, clazz, consumer));
        }
        return result;
    }

    /**************************************************BO转response（结束）**************************************************************************************/
    /**************************************************BO转POJO（开始）**************************************************************************************/

    @SneakyThrows
    public static <T> T transformLongToDate(Object source, final Class<T> clazz) {
        if (ObjectUtils.isEmpty(source)) {
            return null;
        }
        Objects.requireNonNull(clazz);
        T target = transform(source, clazz);

        BeanWrapper sourceInfo = new BeanWrapperImpl(source);
        BeanWrapper targetInfo = new BeanWrapperImpl(clazz);

        PropertyDescriptor[] srcPds = sourceInfo.getPropertyDescriptors();
        PropertyDescriptor[] tarPds = targetInfo.getPropertyDescriptors();

        for (PropertyDescriptor srcPd : srcPds) {
            Method readMethod = srcPd.getReadMethod();
            for (PropertyDescriptor tarPd : tarPds) {
                Method writeMethod = tarPd.getWriteMethod();
                Method tReadMethod = tarPd.getReadMethod();
                if (ObjectUtils.isEmpty(readMethod.invoke(source))) {
                    continue;
                }
                if (!srcPd.getName().equals(tarPd.getName())) {
                    continue;
                }
                if (!tReadMethod.getName().equals(readMethod.getName())) {
                    continue;
                }
                if (!readMethod.getReturnType().equals(Long.class)) {
                    continue;
                }
                if (tarPd.getPropertyType().equals(Date.class)) {
                    Object result = readMethod.invoke(source);
                    writeMethod.invoke(target, longToDate((Long) result));
                }
            }
        }
        return target;
    }

    /**
     * 格式：年－月－日 小时:分钟:秒
     */
    public static final String FORMAT_ONE = "yyyy-MM-dd HH:mm:ss";

    public static Date longToDate(long time) {
        String result = new SimpleDateFormat(FORMAT_ONE).format(new Date(time));
        DateFormat sdf = new SimpleDateFormat(FORMAT_ONE);
        try {
            return sdf.parse(result);
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * Long类型Date转为类型
     *
     * @param srcList
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> List<T> transformLongToDate(List<? extends Object> srcList, final Class<T> clazz) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> list = Lists.newArrayList();
        for (Object source : srcList) {
            list.add(transformLongToDate(source, clazz));
        }
        return list;
    }
    /**************************************************BO转POJO（结束）**************************************************************************************/
    /**************************************************POJO转BO（开始）**************************************************************************************/

    @SneakyThrows
    public static <T> T transformDateToLong(Object source, final Class<T> clazz) {
        if (ObjectUtils.isEmpty(source)) {
            return null;
        }
        Objects.requireNonNull(clazz);

        T target = transform(source, clazz);

        BeanInfo sourceInfo = Introspector.getBeanInfo(source.getClass());
        BeanInfo targetInfo = Introspector.getBeanInfo(clazz);

        PropertyDescriptor[] srcPds = sourceInfo.getPropertyDescriptors();
        PropertyDescriptor[] tarPds = targetInfo.getPropertyDescriptors();

        for (PropertyDescriptor srcPd : srcPds) {
            Method readMethod = srcPd.getReadMethod();
            for (PropertyDescriptor tarPd : tarPds) {
                Method writeMethod = tarPd.getWriteMethod();
                Method tReadMethod = tarPd.getReadMethod();
                if (!srcPd.getName().equals(tarPd.getName())) {
                    continue;
                }
                if (!tReadMethod.getName().equals(readMethod.getName())) {
                    continue;
                }
                if (!readMethod.getReturnType().equals(Date.class)) {
                    continue;
                }

                if (tarPd.getPropertyType().equals(Long.class)) {
                    Object result = readMethod.invoke(source);
                    Long value = result == null ? 0L : ((Date) result).getTime();
                    writeMethod.invoke(target, value);
                }

                if (tarPd.getPropertyType().equals(long.class)) {
                    Object result = readMethod.invoke(source);
                    long value = result == null ? 0L : ((Date) result).getTime();
                    writeMethod.invoke(target, value);
                }
            }
        }

        return target;
    }

    public static <T> List<T> transformDateToLong(List<? extends Object> srcList, final Class<T> clazz) {
        if (CollectionUtils.isEmpty(srcList)) {
            return Collections.emptyList();
        }
        List<T> list = Lists.newArrayList();
        for (Object source : srcList) {
            list.add(transformDateToLong(source, clazz));
        }
        return list;
    }

    /**************************************************POJO转BO（结束）**************************************************************************************/


    private static Map<Class<?>, Supplier<Object>> typeEnum = Maps.newConcurrentMap();

    static {
        typeEnum.put(String.class, () -> StringUtils.EMPTY);
        typeEnum.put(Byte.class, () -> 0);
        typeEnum.put(Short.class, () -> 0);
        typeEnum.put(Integer.class, () -> 0);
        typeEnum.put(Long.class, () -> 0L);
        typeEnum.put(Float.class, () -> 0.0f);
        typeEnum.put(Double.class, () -> 0.0);
        typeEnum.put(Boolean.class, () -> false);
        typeEnum.put(Date.class, Date::new);
    }

    public static <T> T replaceIfNull(T vo) {
        Field[] fields = vo.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            fields[i].setAccessible(true);
            Type type = fields[i].getGenericType();
            if (typeEnum.containsKey(type)) {
                try {
                    Object value = fields[i].get(vo);
                    if (ObjectUtils.isEmpty(value)) {
                        fields[i].set(vo, typeEnum.get(type).get());
                    }
                } catch (Exception e) {
                    throw new BusinessException("对象替换空字段失败！");
                }
            }
        }
        return vo;
    }


    private static <T> void getAllFields(Class<T> vo, List<Field> fieldList) {
        if (Object.class.equals(vo) || vo == null) {
            return;
        }
        fieldList.addAll(Arrays.asList(vo.getDeclaredFields()));
        getAllFields(vo.getSuperclass(), fieldList);
    }


    public static <T> Collection<T> replaceIfNull(Collection<T> collection) {
        for (Object t : collection) {
            replaceIfNull(t);
        }
        return collection;
    }

    public static <T, R> String getBeanFieldName(Fn<T, R> fn) {
        return Reflections.fnToFieldName(fn);
    }

    public static <T, R> String getBeanMethodName(Fn<T, R> fn) {
        return Reflections.fnToMethodName(fn);
    }


    public static <T, R> String downLineToCamel(String downLine) {
        StringBuilder result = new StringBuilder();
        boolean nextUpper = false; // 标记当前字符后面是否需要大写

        for (int i = 0; i < downLine.length(); i++) {
            char c = downLine.charAt(i);
            if (c == '_') {
                nextUpper = true;
            } else {
                if (nextUpper) {
                    result.append(Character.toUpperCase(c));
                    nextUpper = false;
                } else {
                    result.append(Character.toLowerCase(c));
                }
            }
        }
        return result.toString();
    }

    public static <T, R> String camelToDownLine(String camel) {
        StringBuilder result = new StringBuilder();
        result.append(Character.toLowerCase(camel.charAt(0)));

        for (int i = 1; i < camel.length(); i++) {
            char currentChar = camel.charAt(i);
            if (Character.isUpperCase(currentChar)) {
                result.append('_');
                result.append(Character.toLowerCase(currentChar));
            } else {
                result.append(currentChar);
            }
        }
        return result.toString();
    }

    public static <T, R> String camelToDownLine(Fn<T, R> fn) {
        String camel = Reflections.fnToFieldName(fn);
        return camelToDownLine(camel);
    }
}
